home *** CD-ROM | disk | FTP | other *** search
- /* TwistDownList.c */
- /*
- * List In A List Sample
- * TwistDownList.c
- * Copyright © 1993-94 Apple Computer Inc.
- *
- * TwistDownList manages all aspects of the "twist-down" list, a one-column text
- * list where each row contains a triangular button. The button may be in one of
- * two states: closed and opened. When opened, sub-elements to this row are
- * displayed. The twist-down list is generally based on code from NewsWatcher
- * by Steve Falkenburg, but Steve says that the code was originally written by
- * John Norstad.
- *
- * Note that you must provide a LDEF stub resource in your application resource
- * file. See ListInAList.r for an example.
- *
- * Because of the way that the callback LDEF is managed, it must be in the
- * application's root segment. If this is inappropriate, the segement resource
- * must be marked "preload" and "locked" -- otherwise, your program will crash.
- */
- #include "TwistDownList.h"
- #include <Palettes.h>
- #include <GestaltEqu.h>
- #ifdef __powerc
- /*
- * PowerPC System area locations in "Get" and "Set" functions. They are
- * defined in LowMem.h
- */
- #include <LowMem.h>
- #else
- #ifdef MPW
- /*
- * MPW requires a reference to a low memory global to set hiliting
- * Think C and MetroWorks includes SysEqu.h in the default MacHeaders file.
- */
- #include <SysEqu.h>
- #include <Resources.h>
- #endif
- #endif
-
- #ifndef MONOCHROME_FILL
- #define MONOCHROME_FILL 0
- #endif
- /*
- * A common List Selection flag variation that equates Shift and Command keys.
- */
- #define SELECTION_FLAGS \
- (lUseSense | lNoRect | lNoExtend | lNoNilHilite | lDoVAutoscroll)
- /*
- * SELECTION_FLAGS, by default, allows a single list cell to be selected.
- */
- #ifndef SELECTION_FLAGS
- #define SELECTION_FLAGS (lOnlyOne | lNoNilHilite | lDoVAutoscroll)
- #endif
- /*
- * kAnimationDelay is the number of ticks to display the intermediate
- * "twisting" glyph.
- */
- #define kAnimationDelay 3L
- /*
- * The triangle gap parameters define the amount of space to display to the
- * left and right of the twist-down triangle. The "outside gap" is to the left
- * on Roman-alphabet scripts and on the right on Hebrew or Arabic scripts.
- * The default values are suitable for small font sizes, but could be increased
- * for large sizes.
- */
- #define kTriangleOutsideGap (1) /* From margin to button */
- #define kTriangleInsideGap (2) /* From button to text */
- #define kScrollBarWidth (16) /* Width of a scroll bar */
-
- /*
- * The List's userHandle contains our private context information. The PolyHandles
- * are used to draw the "triangle" buttons. Note that they are drawn to the list
- * cell height. When it changes, the triangles will be reconstructed. The fontSize,
- * fontNumber, and isLeftJustify variables are used to draw the list cell content.
- */
- struct TwistDownPrivateRecord {
- TwistDownDrawProc drawProc; /* This draws the cell data */
- PolyHandle openTriangle; /* The "expanded" button */
- PolyHandle closedTriangle; /* The "closed" button */
- PolyHandle intermediateTriangle; /* The "expanding" button */
- short tabIndent; /* NewTwistDownList param */
- short fontSize; /* for TextSize */
- short fontNumber; /* for TextFont */
- Boolean canHiliteSelection; /* TRUE if hilite ok */
- Boolean isLeftJustify; /* GetSysJust value */
- short triangleWidth; /* Twist-down button width */
- };
- typedef struct TwistDownPrivateRecord TwistDownPrivateRecord,
- *TwistDownPrivatePtr, **TwistDownPrivateHdl;
-
- /*
- * The de-referenced private data record is too long to type each time it appears
- * so it will be defined by the PRIVATE macro.
- */
- #define PRIVATE (**((TwistDownPrivateHdl) ((**theList).userHandle)))
- /*
- * These macros simplify access to the flag word in the list element.
- */
- #define SetTDFlag(elementHdl, mask) ((**elementHdl).flag |= (mask))
- #define ClearTDFlag(elementHdl, mask) ((**elementHdl).flag &= ~(mask))
- #define InvertTDFlag(elementHdl, mask) ((**elementHdl).flag ^= (mask))
- #define TestTDFlag(elementHdl, mask) (((**elementHdl).flag & (mask)) != 0)
- /*
- * The proper way to build a compiled-in LDEF is to plug a transfer address into a
- * stub code resource. The StubRecord must track any changes in the LDEF resource.
- * The #pragma statements are defined only for Power PC: they give errors on
- * Think C.
- */
- #ifdef __powerc
- #pragma options align=mac68k
- #endif
- struct StubRecord {
- long lea; /* Lea (pc)+8,a0 */
- short movea; /* Movea.l (a0),a0 */
- short jmp; /* jmp (a0) */
- ProcPtr ldefAddress; /* dc.l 0 */
- };
- typedef struct StubRecord **StubHandle;
- #ifdef __powerc
- #pragma options align=reset
- #endif
-
- /*
- * Local (private) functions.
- */
- /*
- * Dispose of a PolyHandle. This must be a macro. The argument
- * may not have side-effects.
- */
- #define ForgetPoly(thePoly) do { \
- if (thePoly != NULL) { \
- KillPoly(thePoly); \
- thePoly = NULL; \
- } \
- } while (0)
- #define height(r) ((r).bottom - (r).top)
- #define width(r) ((r).right - (r).left)
-
- static short CountVisibleElements(
- TwistDownHdl twistDownHandle
- );
- static void ClearSelectedElementBit(
- TwistDownHdl twistDownHandle
- );
- static void CopySelectionStateToList(
- ListHandle theList,
- short selectedRow
- );
- static void SetElementsInList(
- ListHandle theList,
- TwistDownHdl twistDownHandle,
- Cell *currentCell
- );
- static pascal void TwistDownLDEF(
- short listMessage,
- Boolean listSelect,
- Rect *listRect,
- Cell listCell,
- short listDataOffset,
- short listDataLen,
- ListHandle listHandle
- );
- static void DrawTriangle(
- PolyHandle polyHandle,
- Point polyPoint,
- Boolean isSelected
- );
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * NewTwistDownList
- *
- * Create a twist-down list. Before calling, you must set the port to the current
- * window and specify the font and font size that is to be used to draw the list.
- * NewTwistDownList creates an empty one-column list with a vertical scroll bar
- * and no grow box. Only one item may be selected at a time; but this could
- * be changed by the application without difficulty.
- *
- * Note: unlike LNew, the viewRect includes the vertical scroll bar. The frame
- * and focus rectangles should be drawn outside of the list's rView.
- *
- * The tabIndent parameter should be set to the amount to indent successive levels
- * (zero means no indentation). Setting it to the widMax value from the current
- * font seems reasonable.
- *
- * canHiliteSelection should be TRUE for normal selection (the selection is
- * hilited). It should be FALSE if you want to supress selection. Because
- * hirearchical lists often "select" by revealing a sub-topic, this may be
- * more reasonable in many cases.
- *
- * isLeftJustify should be set TRUE for systems using the Roman alphabet. It would
- * be set FALSE for right-to-left languages such as Arabic and Hebrew. It is used
- * to configure the direction of the buttons and the location of text within
- * the displayed list cell.
- */
- ListHandle
- NewTwistDownList(
- const Rect *viewRect,
- TwistDownDrawProc drawProc,
- unsigned short tabIndent,
- Boolean canHiliteSelection,
- Boolean isLeftJustify
- )
- {
- register TwistDownPrivatePtr privatePtr;
- ListHandle theList;
- short listHeight;
- Point cellSize;
- Rect dataBounds;
- Rect listRect;
- FontInfo info;
- short listFontHeight;
- GrafPtr currentPort;
- ProcPtr listDefProc;
- StubHandle stubHandle;
-
- theList = NULL;
- GetPort(¤tPort);
- GetFontInfo(&info);
- listFontHeight = info.ascent + info.descent + info.leading;
- /*
- * Define the list drawing area. If the list viewRect.bottom
- * is less than the portRect.bottom, adjust the list area height
- * integral number of rows will be drawn. If equal, the list
- * area abuts the bottom of the display window and we shouldn't
- * change the bottom or the scroll bars will look wierd.
- */
- listRect = *viewRect;
- listRect.right -= kScrollBarWidth;
- SetPt(&cellSize, width(listRect), listFontHeight);
- if (listRect.bottom < currentPort->portRect.bottom) {
- listHeight = height(listRect);
- listHeight -= (listHeight % listFontHeight);
- listRect.bottom = listRect.top + listHeight;
- }
- /*
- * Define a one-column list.
- */
- #ifdef __powerc
- /*
- * Create a universal routine descriptor for our private LDEF. This is
- * the general model for all pointer-to-function references.
- */
- listDefProc = (ProcPtr) NewListDefProc(TwistDownLDEF);
- #else
- listDefProc = (ProcPtr) TwistDownLDEF;
- #endif
- stubHandle = (StubHandle) GetResource('LDEF', LDEF_Stub);
- if (stubHandle == NULL)
- goto exit; /* Failure */
- (**stubHandle).ldefAddress = listDefProc;
- SetRect(&dataBounds, 0, 0, 1, 0);
- theList = LNew(
- &listRect, /* Viewing area */
- &dataBounds, /* Rows and col's */
- cellSize, /* Element size */
- LDEF_Stub, /* Callback defproc */
- currentPort, /* Display window */
- TRUE, /* Draw it */
- FALSE, /* No grow box */
- FALSE, /* No horiz scroll */
- TRUE /* Vertical scroll */
- );
- if (theList == NULL)
- goto exit;
- (**theList).selFlags = SELECTION_FLAGS;
- (**theList).refCon = 0; /* Paranoia */
- (**theList).userHandle = NewHandleClear(sizeof (TwistDownPrivateRecord));
- if ((**theList).userHandle == NULL)
- goto failure;
- privatePtr = (TwistDownPrivatePtr) (*(**theList).userHandle);
- #define PRIV (*privatePtr)
- PRIV.drawProc = drawProc;
- PRIV.tabIndent = tabIndent;
- PRIV.canHiliteSelection = canHiliteSelection;
- PRIV.isLeftJustify = isLeftJustify;
- PRIV.fontNumber = currentPort->txFont;
- PRIV.fontSize = currentPort->txSize;
- #undef PRIV
- CreateTwistDownButtons(theList);
- goto exit;
- failure:
- DisposeTwistDownList(theList);
- theList = NULL;
- exit: return (theList);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DisposeTwistDownList
- *
- * Dispose of the list and our private data.
- */
- void
- DisposeTwistDownList(
- ListHandle theList
- )
- {
- if (theList != NULL) {
- if ((**theList).userHandle != NULL) {
- ForgetPoly(PRIVATE.openTriangle);
- ForgetPoly(PRIVATE.closedTriangle);
- ForgetPoly(PRIVATE.intermediateTriangle);
- DisposeHandle((Handle) (**theList).userHandle);
- (**theList).userHandle = NULL;
- }
- LDispose(theList);
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CreateTwistDownButtons
- *
- * CreateTwistDownButtons
- * This function creates the three states of the twist-down button that are drawn
- * in the display list.
- *
- * CreateTwistDownButtons is called when the list is created. The application must
- * call it directly after changing the list cell height (by calling LSize).
- *
- * Note that the port and text drawing characteristics must have been set.
- */
- void
- CreateTwistDownButtons(
- ListHandle theList
- )
- {
- short buttonSize;
- short halfSize;
- short intermediateSize;
- FontInfo info;
-
- ForgetPoly(PRIVATE.openTriangle);
- ForgetPoly(PRIVATE.closedTriangle);
- ForgetPoly(PRIVATE.intermediateTriangle);
- GetFontInfo(&info);
- buttonSize = info.ascent; /* The "show sublist" button */
- buttonSize &= ~1; /* Round down to an even number */
- halfSize = buttonSize / 2;
- intermediateSize = (buttonSize * 3) / 4;
- PRIVATE.openTriangle = OpenPoly();
- MoveTo(0, halfSize);
- LineTo(buttonSize, halfSize);
- LineTo(halfSize, buttonSize);
- LineTo(0, halfSize);
- ClosePoly();
- if (PRIVATE.isLeftJustify) { /* Roman alphabet triangles */
- PRIVATE.closedTriangle = OpenPoly();
- MoveTo(halfSize, 0);
- LineTo(buttonSize, halfSize);
- LineTo(halfSize, buttonSize);
- LineTo(halfSize, 0);
- ClosePoly();
- PRIVATE.intermediateTriangle = OpenPoly();
- MoveTo(intermediateSize, 0);
- LineTo(intermediateSize, intermediateSize);
- LineTo(0, intermediateSize);
- LineTo(intermediateSize, 0);
- ClosePoly();
- }
- else { /* Arabic/Hebrew triangles */
- PRIVATE.closedTriangle = OpenPoly();
- MoveTo(buttonSize - halfSize, 0);
- LineTo(0, halfSize);
- LineTo(buttonSize - halfSize, buttonSize);
- LineTo(buttonSize - halfSize, 0);
- ClosePoly();
- PRIVATE.intermediateTriangle = OpenPoly();
- MoveTo(buttonSize - intermediateSize, 0);
- LineTo(buttonSize - intermediateSize, intermediateSize);
- LineTo(buttonSize, intermediateSize);
- LineTo(buttonSize - intermediateSize, 0);
- ClosePoly();
- }
- /*
- * Remember the width of the "button" area.
- */
- PRIVATE.triangleWidth =
- (**PRIVATE.openTriangle).polyBBox.right
- + kTriangleOutsideGap
- + kTriangleInsideGap;
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DoTwistDownClick
- *
- * DoTwistDownClick handles all processing after a click in the list window.
- * It returns an indication of the user action:
- * kTwistDownNotInList
- * The click was not in this list. Your application may ignore this click or
- * take other appropriate action.
- * kTwistDownNoClick
- * The user released the mouse outside of the list area: this click should be
- * ignored. (It may have been a click in the scroll bar.)
- * kTwistDownButtonClick
- * The user clicked on the twist-down button. The list will be expanded or
- * contracted as appropriate.
- * kTwistDownClick
- * The user clicked (once) on a list datum. The application should treat this
- * as an item selection.
- * kTwistDownDoubleClick
- * The user double-clicked on a list datum. The application should open this
- * item or take other appropriate action.
- *
- * If DoTwistDownClick returns kTwistDownButtonClick, kTwistDownClick, or
- * kTwistDownDoubleClick, selectedListCell will be set to the cell that the user
- * clicked on.
- */
- TwistDownClickState
- DoTwistDownClick(
- ListHandle theList,
- const EventRecord *eventRecordPtr,
- Cell *selectedListCell
- )
- {
- Cell theCell;
- Rect hitRect;
- Boolean inHitRect;
- Boolean newInHitRect;
- short cellHeight;
- short visibleTop;
- TwistDownHdl twistDownHandle;
- Point mousePt;
- TwistDownClickState result;
- long finalTicks;
-
- mousePt = eventRecordPtr->where;
- GlobalToLocal(&mousePt);
- hitRect = (**theList).rView;
- hitRect.right += kScrollBarWidth;
- if (PtInRect(mousePt, &hitRect) == FALSE)
- result = kTwistDownNotInList;
- else {
- /*
- * Set hitRect to the area of the list that contains the twist-down
- * triangle button. Note that this presumes a list where only column
- * zero is displayed.
- */
- if (PRIVATE.isLeftJustify) {
- hitRect.right = (**theList).rView.left
- + (**theList).indent.h
- + PRIVATE.triangleWidth;
- }
- else {
- hitRect.left = (**theList).rView.right
- - (**theList).indent.h
- - PRIVATE.triangleWidth;
- }
- inHitRect = FALSE;
- if (PtInRect(mousePt, &hitRect)) {
- /*
- * It's in a the triangle area. Find the selected cell and check
- * whether this cell's element has a visible twist-down button.
- */
- visibleTop = (**theList).visible.top;
- cellHeight = (**theList).cellSize.v;
- theCell.h = 0; /* This has the visual content */
- theCell.v =
- ((mousePt.v - (**theList).rView.top) / cellHeight)
- + visibleTop;
- /*
- * Set inHitRect TRUE if there is a sub-list button here.
- * Note: it is possible to have a button but no actual sub-list
- * (consider an empty folder in a disk hierarchy: the presence
- * of the button indicates "this is a folder" to the user).
- */
- twistDownHandle = GetTwistDownElementHandle(theList, theCell);
- if (twistDownHandle != NULL
- && TestTDFlag(twistDownHandle, kHasTwistDown))
- inHitRect = TRUE;
- }
- if (inHitRect == FALSE) {
- /*
- * This cell doesn't have an expansion triangle, or the user did
- * not click in the button area. Just call the normal list click
- * handler to manage the scroll bars. Set result appropriately.
- */
- result = (LClick(mousePt, eventRecordPtr->modifiers, theList))
- ? kTwistDownDoubleClick
- : kTwistDownClick;
- }
- else {
- /*
- * The user clicked on in the twist-down button area. Simulate a
- * button click and track the mouse as it wanders in and out of the
- * button area. (inHitRect is true at this point). Whenever the
- * button selection state changes, call LDraw to redraw the
- * twist-down triangle. This is the way to simulate TrackControl.
- */
- SetTDFlag(twistDownHandle,
- (kDrawButtonFilled | kOnlyRedrawButton));
- LDraw(theCell, theList);
- /*
- * Set hitRect to the dimensions of the twist-down button.
- */
- hitRect.top =
- ((theCell.v - visibleTop) * cellHeight)
- + (**theList).rView.top;
- hitRect.bottom = hitRect.top + cellHeight;
- /*
- * Track the mouse while it still down: if it moves into the
- * triangle rectangle, redraw it filled, if it moves out of the
- * triangle, redraw it unfilled.
- */
- if (StillDown()) {
- while (WaitMouseUp()) {
- GetMouse(&mousePt);
- newInHitRect = PtInRect(mousePt, &hitRect);
- if (newInHitRect != inHitRect) {
- /*
- * The mouse moved in or out of the triangle.
- */
- InvertTDFlag(twistDownHandle, kDrawButtonFilled);
- LDraw(theCell, theList);
- inHitRect = newInHitRect;
- }
- }
- }
- /*
- * The user released the mouse.
- */
- if (inHitRect == FALSE) {
- /*
- * Normally, drawButtonFilled will be clear. It can be set,
- * however, if the user clicks so briefly on the triangle
- * that the StillDown() test above is FALSE.
- */
- if (TestTDFlag(twistDownHandle, kDrawButtonFilled)) {
- ClearTDFlag(twistDownHandle, kDrawButtonFilled);
- LDraw(theCell, theList);
- }
- result = kTwistDownNoClick;
- }
- else {
- /*
- * The user released the mouse in the expansion triangle.
- * Draw an intermediate "animation" triangle. Then call
- * ExpandOrCollapseTwistDownList which will redraw the
- * button in its new state.
- */
- SetTDFlag(twistDownHandle,
- (kDrawIntermediate | kEraseButtonArea));
- LDraw(theCell, theList);
- Delay(kAnimationDelay, &finalTicks);
- ClearTDFlag(twistDownHandle,
- (kDrawIntermediate | kDrawButtonFilled | kEraseButtonArea));
- ExpandOrCollapseTwistDownList(theList, theCell);
- result = kTwistDownButtonClick;
- *selectedListCell = theCell;
- }
- ClearTDFlag(twistDownHandle, kOnlyRedrawButton);
- }
- }
- return (result);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ExpandOrCollapseTwistDownList
- *
- * ExpandOrCollapseTwistDownList modifies the "show sublist" flag for the selected
- * cell and rebuilds the visual display.
- */
- void
- ExpandOrCollapseTwistDownList(
- ListHandle theList,
- Cell selectedListCell
- )
- {
- TwistDownHdl twistDownHandle;
-
- twistDownHandle = GetTwistDownElementHandle(theList, selectedListCell);
- if (twistDownHandle != NULL
- && TestTDFlag(twistDownHandle, kHasTwistDown)) {
- InvertTDFlag(twistDownHandle, kShowSublist);
- /*
- * Redraw the twist-down button in its new state.
- */
- ClearTDFlag(twistDownHandle, kDrawButtonFilled);
- SetTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
- LDraw(selectedListCell, theList);
- ClearTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
- /*
- * If some other part of the list will change, rebuild the
- * List Manager list cells and redraw the list.
- */
- if ((**twistDownHandle).subElement != NULL)
- BuildVisibleList(theList, selectedListCell.v);
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CreateVisibleList
- *
- * CreateVisibleList is called when the list is created. It stores the list
- * head into cell [0, 0] and calls BuildVisibleList to instantiate the display.
- */
- void
- CreateVisibleList(
- ListHandle theList,
- TwistDownHdl twistDownHandle
- )
- {
- Cell theCell;
-
- if ((**theList).dataBounds.bottom == 0) {
- /*
- * Add one row to the list so there is a place for the head element.
- */
- LDoDraw(FALSE, theList);
- LAddRow(1, 0, theList);
- LDoDraw(TRUE, theList);
- }
- SetPt(&theCell, 0, 0);
- LSetCell(&twistDownHandle, sizeof twistDownHandle, theCell, theList);
- BuildVisibleList(theList, 0);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * BuildVisibleList
- *
- * BuildVisibleList is called when the list is created, or when the user clicks on
- * a twist-down button. selectedRow is the first row that needs to be redrawn.
- * To rebuild the entire list, store the list head into cell [0, 0] and call
- * with selectedRow = 0. Note: because people can be rebuilding the list from
- * within a larger sublist context, the list head must be stored in cell [0, 0].
- */
- void
- BuildVisibleList(
- ListHandle theList,
- short selectedRow
- )
- {
- short nRows; /* How many we need to show */
- short currentRows; /* How many are in the list */
- Rect viewRect;
- TwistDownHdl listHead;
- Cell theCell;
-
- LDoDraw(FALSE, theList);
- SetPt(&theCell, 0, 0); /* Get the list head */
- listHead = GetTwistDownElementHandle(theList, theCell);
- ClearSelectedElementBit(listHead);
- CopySelectionStateToList(theList, selectedRow);
- nRows = CountVisibleElements(listHead);
- currentRows = (**theList).dataBounds.bottom;
- if (currentRows > nRows)
- LDelRow(currentRows - nRows, nRows, theList); /* Shrink the list */
- else if (currentRows < nRows)
- LAddRow(nRows - currentRows, currentRows + 1, theList); /* Grow it */
- if (nRows != 0) {
- SetElementsInList(theList, listHead, &theCell);
- }
- LDoDraw(TRUE, theList);
- /*
- * Redraw any elements that are greater than the inserted row.
- */
- viewRect = (**theList).rView;
- viewRect.top += ((selectedRow + 1) - (**theList).visible.top)
- * (**theList).cellSize.v;
- if (viewRect.top < viewRect.bottom)
- InvalRect(&viewRect);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CountVisibleElements
- *
- * CountVisibleElements is a recursive function that returns the number of visible
- * elements in the list. Call with the list head to process the entire list. Note:
- * list elements are visible, but sub-lists are visible only if the button state
- * requests visiblity. This function may be called with a NULL argument without
- * problems. Also note that we ignore the current List Manager cell data, working
- * only with the actual linked list structure. By convention, however, the list
- * head is stored in cell [0, 0].
- */
- static short
- CountVisibleElements(
- TwistDownHdl twistDownHandle
- )
- {
- short result;
-
- result = 0;
- while (twistDownHandle != NULL) {
- ++result;
- if (TestTDFlag(twistDownHandle, kShowSublist))
- result += CountVisibleElements((**twistDownHandle).subElement);
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- return (result);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CountListElements
- *
- * CountListElements returns the number of elements in the list (it counts all
- * elements, visible or not.
- */
- unsigned long
- CountListElements(
- TwistDownHdl twistDownHandle
- )
- {
- unsigned long result;
-
- result = 0;
- while (twistDownHandle != NULL) {
- ++result;
- if ((**twistDownHandle).subElement != NULL)
- result += CountVisibleElements((**twistDownHandle).subElement);
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- return (result);
- }
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ClearSelectedElementBit
- *
- * When we expand or contract the visible list, the relationship between the
- * visual display and the actual data changes. In particular, the selected cell
- * may contain different data. To resolve this dilemna, we copy the current
- * selection from the display list to the data elements, then copy the information
- * back when the new elements are inserted into the list. This is a three-step
- * process. First, we clear out the kSelectedElement bit from the linked list.
- */
- static void
- ClearSelectedElementBit(
- TwistDownHdl twistDownHandle
- )
- {
- while (twistDownHandle != NULL) {
- ClearTDFlag(twistDownHandle, kSelectedElement);
- if ((**twistDownHandle).subElement != NULL)
- ClearSelectedElementBit((**twistDownHandle).subElement);
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ClearSelectedElementBit
- *
- * After clearing the kSelectedElementBit from the element list, we save the
- * "is selected" bit from the display list into the associated element. Note that
- * the cell will be deselected but selection state is restored when the list is
- * rebuilt. The function starts with the row after the selected (clicked on) cell
- * as that cell is not redrawn and, presumably, is correctly hilited.
- */
- static void
- CopySelectionStateToList(
- ListHandle theList,
- short selectedRow
- )
- {
- TwistDownHdl twistDownHandle;
- Cell theCell;
-
- SetPt(&theCell, 0, selectedRow + 1);
- while (LGetSelect(TRUE, &theCell, theList)) {
- twistDownHandle = GetTwistDownElementHandle(theList, theCell);
- if (twistDownHandle != NULL)
- SetTDFlag(twistDownHandle, kSelectedElement);
- LSetSelect(FALSE, theCell, theList);
- ++theCell.v;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * SetElementsInList
- *
- * SetElementsInList is a recursive function that copies visible list elements from
- * the linked list to the List Manager list. The List Manager list was extended so
- * it holds all visible cells. Note that currentCell is a "global" that always
- * contains the current List Manager cell. To process the entire list, set
- * currentCell to [0, 0] and call with the list head. If the list element is
- * selected (from CopySelectionStateToList above), select this list cell.
- */
- static void
- SetElementsInList(
- ListHandle theList,
- TwistDownHdl twistDownHandle,
- Cell *currentCell
- )
- {
- while (twistDownHandle != NULL) {
- LSetCell(
- &twistDownHandle, sizeof twistDownHandle, *currentCell, theList);
- if (TestTDFlag(twistDownHandle, kSelectedElement))
- LSetSelect(TRUE, *currentCell, theList);
- ++currentCell->v;
- if (TestTDFlag(twistDownHandle, kShowSublist)) {
- SetElementsInList(
- theList,
- (**twistDownHandle).subElement,
- currentCell
- );
- }
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * GetTwistDownElementHandle
- *
- * GetTwistDownElementHandle returns the TwistDownHdl that is stored in a List
- * cell. It will return NULL if the cell is out of bounds.
- */
- TwistDownHdl
- GetTwistDownElementHandle(
- ListHandle theList,
- Cell theCell
- )
- {
- TwistDownHdl twistDownHandle;
- short dataSize;
-
- dataSize = sizeof twistDownHandle;
- LGetCell(&twistDownHandle, &dataSize, theCell, theList);
- if (dataSize != sizeof twistDownHandle)
- twistDownHandle = NULL;
- return (twistDownHandle);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * MakeTwistDownElement
- *
- * MakeTwistDownElement adds an element to a linked list. It is designed to create
- * elements in a hierarchical linked-list where each element may be followed by a
- * successor (on the same "level") and/or by a child list (on a lower "level").
- * Note that MakeTwistDownElement is only concerned with the linked list. It
- * does not change the List Manager list.
- *
- * The parameters are as follows:
- * previousElement
- * This is a handle to the predecessor to this element. previousElement is
- * NULL If this is the first element in a list or the first on a level.
- * indentLevel
- * This is the indentation-level of this list element. It is only
- * used to tab the list elements on the visual display. If you don't
- * want tabbing, set tabIndent to zero when the list was created.
- * dataLength
- * This is the length of the list element datum.
- * dataPtr
- * This is a pointer to the first byte of the list element datum. If NULL,
- * a data block of the requisite size will be created, but the caller is
- * responsible for filling it in.
- * result
- * If MakeElement succeeds, result will will contain a handle to the
- * list element it created. This is needed to create a successor
- * element.
- */
- OSErr
- MakeTwistDownElement(
- TwistDownHdl previousElement,
- short indentLevel,
- unsigned short dataLength,
- Ptr dataPtr,
- TwistDownHdl *result
- )
- {
- TwistDownHdl twistDownHandle;
-
- twistDownHandle = (TwistDownHdl) NewHandle(
- sizeof (TwistDownRecord)
- - sizeof (unsigned char)
- + dataLength
- );
- if (twistDownHandle != NULL) {
- if (previousElement != NULL)
- (**previousElement).nextElement = twistDownHandle;
- (**twistDownHandle).nextElement = NULL;
- (**twistDownHandle).subElement = NULL;
- (**twistDownHandle).flag = 0;
- (**twistDownHandle).indentLevel = indentLevel;
- (**twistDownHandle).dataLength = dataLength;
- if (dataPtr != NULL)
- BlockMove(dataPtr, (**twistDownHandle).data, dataLength);
- *result = twistDownHandle;
- }
- return (MemError());
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DisposeTwistDownHdl
- *
- * DisposeTwistDownHdl disposes of the linked list that has the argument at
- * its head, and of all sublists linked to this list. Note that it presumes
- * that the list element does not, itself, contain Ptr or Handle data that
- * must be disposed. (C++ overloading would be useful here.)
- */
- void
- DisposeTwistDownHdl(
- TwistDownHdl twistDownHandle,
- DisposeTwistDownCallback userProc,
- void *userData
- )
- {
- TwistDownHdl nextElement;
- TwistDownHdl subElement;
-
- while (twistDownHandle != NULL) {
- nextElement = (**twistDownHandle).nextElement;
- subElement = (**twistDownHandle).subElement;
- if (userProc != NULL)
- (*userProc)(twistDownHandle, userData);
- DisposeHandle((Handle) twistDownHandle);
- if (subElement != NULL)
- DisposeTwistDownHdl(subElement, userProc, userData);
- twistDownHandle = nextElement;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * TwistDownLDEF
- *
- * Draw the twist-down list cell. Note that we have to draw the expansion button
- * in one of five states: (expanded/compressed), (normal/selected), and an
- * animation state.
- */
- pascal void
- TwistDownLDEF(
- short listMessage,
- Boolean listSelect,
- Rect *listRect,
- Cell listCell,
- short listDataOffset,
- short listDataLen,
- ListHandle theList
- )
- {
- #pragma unused (listCell, listDataOffset)
-
- short indent;
- TwistDownHdl twistDownHandle;
- register TwistDownPtr twistDownPtr;
- GrafPtr grafPtr;
- short saveFontNumber;
- short saveFontSize;
- short cellSize;
- PolyHandle polyHandle;
- Point polyPoint;
- Rect viewRect;
- signed char elementLockState;
- Boolean onlyRedrawButton;
- FontInfo info;
- #define TestFlag(flagBit) (((*twistDownPtr).flag & (flagBit)) != 0)
-
- switch (listMessage) {
- case lInitMsg:
- /*
- * Initialize the list indentation values. Since the userHandle
- * (which has the TwistDown private data) hasn't been setup yet,
- * we let the current font and font size establish the indentation.
- */
- (**theList).indent.h = 4;
- GetFontInfo(&info);
- (**theList).indent.v = info.ascent;
- break;
- case lCloseMsg:
- if ((**theList).userHandle != NULL) {
- ForgetPoly(PRIVATE.openTriangle);
- ForgetPoly(PRIVATE.closedTriangle);
- ForgetPoly(PRIVATE.intermediateTriangle);
- DisposeHandle((**theList).userHandle);
- (**theList).userHandle = NULL;
- }
- break;
- case lDrawMsg:
- onlyRedrawButton = FALSE;
- if (listDataLen > 0 && (**theList).userHandle != NULL) {
- /*
- * Get the cell content. This is the handle that has the list
- * element. We check that the userHandle has been setup correctly.
- * Note that we don't use LFind (or similar) because the data
- * might not be aligned in the list cell storage. Actually, the
- * data is aligned as we only store Handles in the cells.
- */
- cellSize = sizeof twistDownHandle;
- LGetCell(&twistDownHandle, &cellSize, listCell, theList);
- if (cellSize == sizeof twistDownHandle
- && twistDownHandle != NULL) {
- elementLockState = HGetState((Handle) twistDownHandle);
- HLock((Handle) twistDownHandle);
- twistDownPtr = (*twistDownHandle);
- onlyRedrawButton = TestFlag(kOnlyRedrawButton);
- viewRect = *listRect;
- if (onlyRedrawButton) {
- if (PRIVATE.isLeftJustify) {
- viewRect.right = viewRect.left
- + (**theList).indent.h
- + PRIVATE.triangleWidth;
- }
- else {
- viewRect.left = viewRect.right
- - (**theList).indent.h
- - kTriangleOutsideGap
- - PRIVATE.triangleWidth;
- }
- }
- if (onlyRedrawButton == FALSE || TestFlag(kEraseButtonArea))
- EraseRect(&viewRect);
- if (TestFlag(kHasTwistDown)) {
- /*
- * Draw the expansion triangle in one of
- * its three states.
- */
- polyPoint.v = listRect->top + 1;
- if (PRIVATE.isLeftJustify) {
- polyPoint.h = listRect->left
- + (**theList).indent.h
- + kTriangleOutsideGap;
- }
- else {
- polyPoint.h = listRect->right
- - (**theList).indent.h
- - PRIVATE.triangleWidth
- + kTriangleInsideGap;
- }
- if (TestFlag(kDrawIntermediate))
- polyHandle = PRIVATE.intermediateTriangle;
- else if (TestFlag(kShowSublist))
- polyHandle = PRIVATE.openTriangle;
- else {
- polyHandle = PRIVATE.closedTriangle;
- }
- DrawTriangle(
- polyHandle,
- polyPoint,
- TestFlag(kDrawButtonFilled)
- );
- }
- if (onlyRedrawButton == FALSE
- && (*twistDownPtr).dataLength > 0) {
- /*
- * Indent the text to show the depth of the hierarchy.
- */
- indent = (**theList).indent.h
- + PRIVATE.triangleWidth
- + (PRIVATE.tabIndent * (*twistDownPtr).indentLevel);
- viewRect = *listRect;
- /*
- * Build a display rectangle for the cell text and set the
- * pen to the leftmost position of the text. Note that
- * this right-justifies text for Arabic and Hebrew.
- */
- if (PRIVATE.isLeftJustify)
- viewRect.left += indent;
- else {
- viewRect.right -= indent;
- }
- GetPort(&grafPtr);
- saveFontNumber = grafPtr->txFont;
- saveFontSize = grafPtr->txSize;
- TextFont(PRIVATE.fontNumber);
- TextSize(PRIVATE.fontSize);
- if (PRIVATE.drawProc == NULL) {
- if (PRIVATE.isLeftJustify) {
- MoveTo(
- viewRect.left,
- viewRect.top + (**theList).indent.v
- );
- }
- else {
- MoveTo(
- viewRect.right
- - TextWidth(
- (*twistDownPtr).data,
- 0,
- (*twistDownPtr).dataLength
- ),
- viewRect.top + (**theList).indent.v
- );
- }
- DrawText(
- (*twistDownPtr).data,
- 0,
- (*twistDownPtr).dataLength
- );
- }
- else {
- (PRIVATE.drawProc)(
- theList,
- (const Ptr) (*twistDownPtr).data,
- (*twistDownPtr).dataLength,
- &viewRect
- );
- }
- TextFont(saveFontNumber);
- TextSize(saveFontSize);
- } /* Drawing cell */
- HSetState((Handle) twistDownHandle, elementLockState);
- } /* Have list element */
- } /* Have cell data */
- if (listSelect == FALSE || onlyRedrawButton)
- break;
- /* Continue to do hilite */
- case lHiliteMsg:
- if (PRIVATE.canHiliteSelection) {
- #ifdef THINK_C
- HiliteMode &= ~(1 << hiliteBit);
- #elif defined(__powerc)
- /*
- * PowerPC uses accessor functions to test and manipulate
- * low-memory variables.
- */
- LMSetHiliteMode(LMGetHiliteMode() & ~(1 << hiliteBit));
- #else /* MPW */
- *((char *) HiliteMode) &= ~(1 << hiliteBit); /* IM V-61 */
- #endif
- viewRect = *listRect;
- if (PRIVATE.isLeftJustify)
- viewRect.left += ((**theList).indent.h + PRIVATE.triangleWidth);
- else {
- viewRect.right -= ((**theList).indent.h + PRIVATE.triangleWidth);
- }
- InvertRect(&viewRect);
- }
- break;
- }
- #undef TestFlag
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DrawTriangle
- *
- * DrawTriangle
- * Draw the polygon and fill it so it looks a bit like the Finder. If isSelected
- * is TRUE, the polygon is always black-filled. Else, on color monitors, a light
- * gray is chosen.
- *
- * DrawTriangle uses the DeviceLoop available with System 7 to draw across
- * multiple screens.
- */
-
- typedef struct TriangleInfo {
- PolyHandle polyHandle;
- Point polyPoint;
- } TriangleInfo, *TriangleInfoPtr;
-
- static pascal void DrawThisTriangle(
- short depth,
- short deviceFlags,
- GDHandle targetDevice,
- TriangleInfoPtr triangleInfoPtr
- );
-
- static void
- DrawTriangle(
- PolyHandle polyHandle,
- Point polyPoint,
- Boolean isSelected
- )
- {
- TriangleInfo triangleInfo;
- RgnHandle drawingRgn;
- long savedA5;
-
- /*
- * Refresh A5 so we can use the QuickDraw globals, even if we're called
- * in a non-application context.
- */
- savedA5 = SetCurrentA5();
- triangleInfo.polyHandle = polyHandle;
- triangleInfo.polyPoint = polyPoint;
- OffsetPoly(polyHandle, polyPoint.h, polyPoint.v);
- if (isSelected)
- FillPoly(polyHandle, &qd.black);
- else {
- drawingRgn = NewRgn();
- OpenRgn();
- FramePoly(polyHandle);
- CloseRgn(drawingRgn);
- #ifdef powerc
- /*
- * To call my private drawing function on PowerPC, we must
- * create a private routine descriptor. In a production environment,
- * this would typcally be done once, and myDrawingUPP globalized.
- */
- {
- DeviceLoopDrawingUPP myDrawingUPP;
-
- myDrawingUPP = NewDeviceLoopDrawingProc(DrawThisTriangle);
- DeviceLoop(
- drawingRgn,
- myDrawingUPP,
- (long) &triangleInfo,
- 0
- );
- DisposeRoutineDescriptor(myDrawingUPP);
- }
- #else
- DeviceLoop(
- drawingRgn,
- (DeviceLoopDrawingProcPtr) DrawThisTriangle,
- (long) &triangleInfo,
- 0
- );
- #endif
- DisposeRgn(drawingRgn);
- }
- /*
- * A thicker pen might look better for large font sizes, but it needs
- * to be done in a way that looks good for both button orientations.
- */
- FramePoly(polyHandle);
- OffsetPoly(polyHandle, -polyPoint.h, -polyPoint.v);
- SetA5(savedA5);
- }
-
- static pascal void
- DrawThisTriangle(
- short depth,
- short deviceFlags,
- GDHandle targetDevice,
- TriangleInfoPtr triangleInfoPtr
- )
- {
- RGBColor foreColor;
- RGBColor saveForeColor;
- RGBColor backColor;
- short i;
- Rect polyRect;
- #define TRI (*triangleInfoPtr)
- #pragma unused (deviceFlags, targetDevice)
-
- polyRect = (**TRI.polyHandle).polyBBox;
- LocalToGlobal(& ((Point *) &polyRect)[0]);
- LocalToGlobal(& ((Point *) &polyRect)[1]);
- if (depth > 1) {
- /*
- * We are drawing on a color device (or devices). Fill the unselected
- * triangle with a very light gray. The Finder extends this by filling
- * using the icon color instead of black.
- */
- GetForeColor(&foreColor);
- saveForeColor = foreColor;
- GetBackColor(&backColor);
- #if 1 /* Normal */
- /*
- * This loop sets foreColor to a very light gray.
- */
- for (i = 0; i < 8; i++) {
- if (GetGray(GetGDevice(), &backColor, &foreColor) == FALSE)
- break;
- }
- #else /* Debug: use a medium gray color so the effect is apparent */
- GetGray(GetGDevice(), &backColor, &foreColor);
- #endif
- RGBForeColor(&foreColor);
- FillPoly(TRI.polyHandle, &qd.black);
- RGBForeColor(&saveForeColor);
- }
- else {
- #if MONOCHROME_FILL
- /*
- * We really don't need to do this, but it was useful in debugging
- * the algorithm on a machine with multiple displays. This fills
- * the polygon with a light gray texture on monochrome displays.
- * This is different from the Finder algorithm.
- */
- FillPoly(TRI.polyHandle, (ConstPatternParam) &qd.ltGray);
- #else
- /*
- * Normally, we need only erase the interior of the polygon.
- */
- ErasePoly(TRI.polyHandle);
- #endif
- #undef TRI
- }
- }
-